/**
 * Copyright 2013 ABSir's Studio
 * 
 * All right reserved
 *
 * Create on 2013-6-17 下午4:25:30
 */
package com.absir.bean.inject;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.absir.bean.basis.BeanDefine;
import com.absir.bean.basis.BeanFactory;
import com.absir.bean.basis.BeanScope;
import com.absir.bean.basis.Configure;
import com.absir.bean.config.IBeanDefineAware;
import com.absir.bean.config.IBeanDefineSupply;
import com.absir.bean.config.IBeanFactoryAware;
import com.absir.bean.config.IBeanObjectProcessor;
import com.absir.bean.config.IBeanTypeFilter;
import com.absir.bean.core.BeanDefineMethod;
import com.absir.bean.core.BeanDefineType;
import com.absir.bean.core.BeanFactoryImpl;
import com.absir.bean.inject.value.Bean;
import com.absir.bean.inject.value.Inject;
import com.absir.bean.inject.value.InjectType;
import com.absir.core.kernel.KernelArray;
import com.absir.core.kernel.KernelLang.BreakException;
import com.absir.core.kernel.KernelLang.CallbackBreak;
import com.absir.core.kernel.KernelList;
import com.absir.core.kernel.KernelReflect;

/**
 * @author absir
 * 
 */
@Bean
public class InjectBeanFactory implements IBeanTypeFilter, IBeanDefineSupply, IBeanDefineAware, IBeanObjectProcessor,
		IBeanFactoryAware {

	/** typeSupports */
	private ITypeSupport[] typeSupports = new ITypeSupport[] {};

	/** fieldSupports */
	private IFieldSupport[] fieldSupports = new IFieldSupport[] { new IFieldSupport() {

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.absir.bean.inject.value.IFieldSupport#getInjectInvoker(java.lang
		 * .reflect.Field)
		 */
		@Override
		public InjectInvoker getInjectInvoker(Field field) {
			// TODO Auto-generated method stub
			Inject inject = field.getAnnotation(Inject.class);
			if (inject != null) {
				return new InjectField(field, inject.value(), inject.type());
			}

			return null;
		}
	} };

	/** methodSupports */
	private IMethodSupport[] methodSupports = new IMethodSupport[] { new IMethodSupport() {

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.absir.bean.inject.value.IMethodSupport#supportMethod(java.lang
		 * .reflect.Method)
		 */
		@Override
		public boolean supportMethod(Method method) {
			// TODO Auto-generated method stub
			return method.getAnnotation(Inject.class) != null;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.absir.bean.inject.value.IMethodSupport#getInjectInvoker(java.
		 * lang.reflect.Method)
		 */
		@Override
		public InjectInvoker getInjectInvoker(Method method) {
			// TODO Auto-generated method stub
			Inject inject = method.getAnnotation(Inject.class);
			if (inject != null) {
				return new InjectMethod(method, inject.value(), inject.type());
			}

			return null;
		}
	} };

	/**
	 * @param typeSupports
	 */
	@Inject(type = InjectType.Selectable)
	private void setFieldSupports(ITypeSupport[] typeSupports) {
		this.typeSupports = KernelArray.concat(this.typeSupports, typeSupports);
	}

	/**
	 * @param fieldSupports
	 */
	@Inject(type = InjectType.Selectable)
	private void setFieldSupports(IFieldSupport[] fieldSupports) {
		this.fieldSupports = KernelArray.concat(this.fieldSupports, fieldSupports);
	}

	/**
	 * @param methodSupports
	 */
	@Inject(type = InjectType.Selectable)
	private void setMethodSupports(IMethodSupport[] methodSupports) {
		this.methodSupports = KernelArray.concat(this.methodSupports, methodSupports);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.core.kernel.KernelList.Orderable#getOrder()
	 */
	@Override
	public int getOrder() {
		// TODO Auto-generated method stub
		return Integer.MAX_VALUE;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.android.bean.config.IBeanTypeFilter#match(java.lang.Class)
	 */
	@Override
	public boolean match(Class<?> beanType) {
		// TODO Auto-generated method stub
		return !beanType.isInterface()
				&& (beanType.getAnnotation(Bean.class) != null || beanType.getAnnotation(Configure.class) != null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.bean.config.IBeanDefineSupply#getBeanDefines(com.absir.bean
	 * .core.BeanFactoryImpl, java.lang.Class)
	 */
	@Override
	public List<BeanDefine> getBeanDefines(final BeanFactoryImpl beanFactory, final Class<?> beanType) {
		// TODO Auto-generated method stub
		BeanDefine beanDefineType = null;
		Bean bean = beanType.getAnnotation(Bean.class);
		final List<BeanDefine> beanDefines = new ArrayList<BeanDefine>();
		if (!Modifier.isAbstract(beanType.getModifiers())) {
			if (bean != null) {
				beanDefineType = new InjectBeanDefine(new BeanDefineType(bean.value(), beanType), bean);
				beanDefines.add(beanDefineType);
			}
		}

		final BeanDefine beanDefine = beanDefineType;
		KernelReflect.doWithDeclaredMethods(beanType, new CallbackBreak<Method>() {

			@Override
			public void doWith(Method template) throws BreakException {
				// TODO Auto-generated method stub
				Bean bean = template.getAnnotation(Bean.class);
				if (bean != null) {
					if (beanDefine != null || Modifier.isStatic(template.getModifiers())) {
						String beanName = BeanDefineMethod.getBeanName(bean.value(), template);
						BeanDefine beanDefineMethod = beanFactory.getBeanDefine(beanName);
						if (beanDefineMethod == null
								|| !template.getReturnType().isAssignableFrom(beanDefineMethod.getBeanType())) {
							template = KernelReflect.declaredMethod(beanType, template.getName(),
									template.getParameterTypes());
							if (template != null) {
								template.setAccessible(true);
								beanDefineMethod = new InjectBeanDefine((new BeanDefineMethod(beanName, beanDefine,
										template)), bean);
								beanDefines.add(beanDefineMethod);
							}
						}
					}
				}
			}
		});

		if (beanDefine == null && (bean != null || beanType.getAnnotation(Configure.class) != null)) {
			KernelReflect.doWithDeclaredFields(beanType, new CallbackBreak<Field>() {

				@Override
				public void doWith(Field template) throws BreakException {
					// TODO Auto-generated method stub
					if (Modifier.isStatic(template.getModifiers()) && !Modifier.isFinal(template.getModifiers())
							&& template.getAnnotations().length > 0) {
						InjectAdapter.injectMember(template);
					}
				}
			});

			KernelReflect.doWithDeclaredMethods(beanType, new CallbackBreak<Method>() {

				@Override
				public void doWith(Method template) throws BreakException {
					// TODO Auto-generated method stub
					if (Modifier.isStatic(template.getModifiers()) && template.getAnnotations().length > 0) {
						InjectAdapter.injectMember(template);
					}
				}
			});
		}

		return beanDefines;
	}

	/** injectObservers */
	private final List<InjectObserver> injectObservers = new ArrayList<InjectObserver>();

	/** injectSoftObservers */
	private final List<InjectSoftObserver> injectSoftObservers = new ArrayList<InjectSoftObserver>();

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.bean.config.IBeanDefineAware#registerBeanDefine(com.absir.bean
	 * .core.BeanFactory, com.absir.bean.basis.IBeanDefine)
	 */
	@Override
	public void registerBeanDefine(BeanFactoryImpl beanFactory, BeanDefine beanDefine) {
		// TODO Auto-generated method stub
		changedBeanDefine(beanFactory, beanDefine, true);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.bean.config.IBeanDefineAware#unRegisterBeanDefine(com.absir
	 * .bean.core.BeanFactory, com.absir.bean.basis.IBeanDefine)
	 */
	@Override
	public void unRegisterBeanDefine(BeanFactoryImpl beanFactory, BeanDefine beanDefine) {
		// TODO Auto-generated method stub
		changedBeanDefine(beanFactory, beanDefine, false);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.bean.config.IBeanDefineAware#replaceRegisterBeanDefine(com.
	 * absir.bean.core.BeanFactoryImpl, com.absir.bean.basis.BeanDefine)
	 */
	@Override
	public void replaceRegisterBeanDefine(BeanFactoryImpl beanFactory, BeanDefine beanDefine) {
		// TODO Auto-generated method stub
		changedBeanDefine(beanFactory, beanDefine, true);
	}

	/**
	 * @param beanFactory
	 * @param beanDefine
	 * @param register
	 */
	private synchronized void changedBeanDefine(BeanFactoryImpl beanFactory, BeanDefine beanDefine, boolean register) {
		for (InjectObserver injectObserver : injectObservers) {
			injectObserver.changed(beanFactory, beanDefine, register);
		}

		Iterator<InjectSoftObserver> iterator = injectSoftObservers.iterator();
		while (iterator.hasNext()) {
			if (!iterator.next().changed(beanFactory, beanDefine, register)) {
				iterator.remove();
			}
		}
	}

	/** injectInvokerCaches */
	private final Map<Class<?>, List<InjectInvoker>> injectInvokerCaches = new HashMap<Class<?>, List<InjectInvoker>>();

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.bean.config.IBeanObjectProcessor#processBeanObject(com.absir
	 * .bean.basis.BeanFactory, com.absir.bean.basis.BeanScope,
	 * java.lang.Object)
	 */
	@Override
	public void processBeanObject(final BeanFactory beanFactory, BeanScope beanScope, final Object beanObject) {
		// TODO Auto-generated method stub
		List<InjectInvoker> injectInvokers = beanScope == BeanScope.PROTOTYPE ? injectInvokerCaches.get(beanObject
				.getClass()) : null;
		if (injectInvokers == null) {
			synchronized (beanObject.getClass()) {
				if (beanScope == BeanScope.PROTOTYPE) {
					injectInvokers = injectInvokerCaches.get(beanObject.getClass());
				}

				if (injectInvokers == null) {
					final List<InjectInvoker> sInvokerList = new ArrayList<InjectInvoker>();
					final List<InjectInvoker> iInvokerList = new ArrayList<InjectInvoker>();
					final List<InjectInvoker> sInvokerScope = new ArrayList<InjectInvoker>();
					final List<InjectInvoker> iInvokerScope = new ArrayList<InjectInvoker>();
					final Set<Method> iMethodSet = new HashSet<Method>();

					KernelReflect.doWithClasses(beanObject.getClass(), new CallbackBreak<Class<?>>() {

						@Override
						public void doWith(Class<?> template) throws BreakException {
							// TODO Auto-generated method stub
							for (ITypeSupport typeSupport : typeSupports) {
								InjectInvoker injectInvoker = typeSupport.getInjectInvoker(template);
								if (injectInvoker != null) {
									iInvokerScope.add(injectInvoker);
								}
							}

							for (Field field : template.getDeclaredFields()) {
								if (!Modifier.isFinal(field.getModifiers())) {
									for (IFieldSupport fieldSupport : fieldSupports) {
										InjectInvoker injectInvoker = fieldSupport.getInjectInvoker(field);
										if (injectInvoker != null) {
											field.setAccessible(true);
											if (Modifier.isStatic(field.getModifiers())) {
												sInvokerScope.add(injectInvoker);

											} else {
												iInvokerScope.add(injectInvoker);
											}
										}
									}
								}
							}

							for (Method method : template.getDeclaredMethods()) {
								for (IMethodSupport methodSupport : methodSupports) {
									if (methodSupport.supportMethod(method)) {
										InjectInvoker injectInvoker = null;
										if (Modifier.isStatic(method.getModifiers())) {
											injectInvoker = methodSupport.getInjectInvoker(method);

										} else {
											if (!Modifier.isPrivate(method.getModifiers())
													&& Modifier.isFinal(method.getModifiers())) {
												method = KernelReflect.declaredMethod(beanObject.getClass(),
														method.getName(), method.getParameterTypes());
											}

											if (method != null && !Modifier.isStatic(method.getModifiers())) {
												if (iMethodSet.add(method)) {
													injectInvoker = methodSupport.getInjectInvoker(method);
												}
											}
										}

										if (injectInvoker != null) {
											method.setAccessible(true);
											if (Modifier.isStatic(method.getModifiers())) {
												sInvokerScope.add(injectInvoker);

											} else {
												iInvokerScope.add(injectInvoker);
											}
										}
									}
								}
							}

							KernelList.sortOrderable(sInvokerScope);
							KernelList.sortOrderable(iInvokerScope);
							sInvokerList.addAll(0, sInvokerScope);
							iInvokerList.addAll(0, iInvokerScope);
							sInvokerScope.clear();
							iInvokerScope.clear();
						}
					});

					for (InjectInvoker injectInvoker : sInvokerList) {
						injectInvoker.invoke(beanFactory, beanObject);
						if (injectInvoker.getInjectType() == InjectType.ObServed
								|| injectInvoker.getInjectType() == InjectType.ObServeRealed) {
							injectObservers.add(new InjectObserver(injectInvoker));
						}
					}

					injectInvokers = iInvokerList;
					if (beanScope == BeanScope.PROTOTYPE) {
						injectInvokerCaches.put(beanObject.getClass(), iInvokerList);
					}
				}
			}
		}

		InjectSoftObserver injectSoftObserver = new InjectSoftObserver(beanObject);
		for (InjectInvoker injectInvoker : injectInvokers) {
			injectInvoker.invoke(beanFactory, beanObject);
			if (injectInvoker.getInjectType() == InjectType.ObServed
					|| injectInvoker.getInjectType() == InjectType.ObServeRealed) {
				injectSoftObserver.add(injectInvoker);
			}
		}

		if (!injectSoftObserver.isEmpty()) {
			injectSoftObservers.add(injectSoftObserver);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.android.bean.config.IBeanFactoryAware#beforeRegister(com.absir
	 * .android.bean.core.BeanFactory)
	 */
	@Override
	public void beforeRegister(BeanFactoryImpl beanFactory) {
		// TODO Auto-generated method stub
		InjectAdapter.injectMembers();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.absir.android.bean.config.IBeanFactoryAware#started(com.absir.android
	 * .bean.BeanFactory)
	 */
	@Override
	public void afterRegister(BeanFactoryImpl beanFactory) {
		// TODO Auto-generated method stub
		BeanDefine beanDefineAdapter = beanFactory.getBeanDefine(BeanDefineType.getBeanName(null, InjectAdapter.class));
		beanDefineAdapter.getBeanObject();
		beanFactory.unRegisterBeanDefine(beanDefineAdapter);

		for (BeanDefine beanDefine : beanFactory.getBeanDefines()) {
			if (beanDefine instanceof InjectBeanDefine) {
				if (beanDefine.getBeanScope() == BeanScope.SINGLETON) {
					beanDefine.getBeanObject();
				}
			}

		}

		InjectAdapter.started();
		beanFactory.unRegisterBeanObject(BeanDefineType.getBeanName(null, InjectAdapter.class));
	}
}
